package com.sec.android.app.myfiles.feature.optimizestorage;

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.text.format.Formatter;

import com.samsung.android.sdk.slinkcloud.CloudGatewayMediaStore;
import com.sec.android.app.myfiles.R;
import com.sec.android.app.myfiles.feature.CloudMgr;
import com.sec.android.app.myfiles.feature.OptimizeStorageMgr.StorageDbType;
import com.sec.android.app.myfiles.info.AppFeatures;
import com.sec.android.app.myfiles.module.abstraction.FileRecord;
import com.sec.android.app.myfiles.module.cloud.CloudDbTableInfo;
import com.sec.android.app.myfiles.module.optimizestorage.OptimizeStorageDbTableInfo;
import com.sec.android.app.myfiles.provider.DbTableInfo;
import com.sec.android.app.myfiles.util.FileUtils;
import com.sec.android.app.myfiles.util.PreferenceUtils;

import java.util.ArrayList;
import java.util.Locale;

/**
 * Created by daesu83.kim on 2016-12-19.
 */

public class QueryInfoStore {

    private static final long DUPLICATED_FILE_MINIMUM_SIZE = FileUtils.MEGA_BYTES;

    protected interface QueryInfo {
        StorageDbType getStorageDbType();
        Uri getUri();
        String getSizeColumnName();
        String getNameColumnName();
        String getAllSizeSelection();
        String getDuplicatedFileGroupInfoSelection(boolean isRefresh);
        String getDuplicatedFileGroupInfoOrderBy();
    }


    protected static class MediaProviderQueryInfo implements QueryInfo {

        @Override
        public StorageDbType getStorageDbType() {
            return StorageDbType.MEDIA_PROVIDER;
        }

        @Override
        public Uri getUri() {
            return FileUtils.MEDIA_PROVIDER_URI;
        }

        @Override
        public String getSizeColumnName() {
            return MediaStore.MediaColumns.SIZE;
        }

        @Override
        public String getNameColumnName() {
            return MediaStore.MediaColumns.DISPLAY_NAME;
        }

        @Override
        public String getAllSizeSelection() {
            String sizeColumnName = getSizeColumnName();
            return sizeColumnName + " > 0) GROUP BY (" + sizeColumnName;
        }

        @Override
        public String getDuplicatedFileGroupInfoSelection(boolean isRefresh) {
            String sizeColumnName = getSizeColumnName();
            return sizeColumnName + " >= " + DUPLICATED_FILE_MINIMUM_SIZE + ") GROUP BY " +
                    sizeColumnName + " HAVING (COUNT(*) > " + (isRefresh ? '1' : '0');
        }

        @Override
        public String getDuplicatedFileGroupInfoOrderBy() {
            return getSizeColumnName() + " desc";
        }
    }



    protected static class OptimizeStorageQueryInfo implements QueryInfo {

        @Override
        public StorageDbType getStorageDbType() {
            return StorageDbType.OPTIMIZE_STORAGE;
        }

        @Override
        public Uri getUri() {
            DbTableInfo optimizeStorageTableInfo = OptimizeStorageDbTableInfo.getInstance();
            return Uri.parse(optimizeStorageTableInfo.getUri());
        }

        @Override
        public String getSizeColumnName() {
            DbTableInfo optimizeStorageTableInfo = OptimizeStorageDbTableInfo.getInstance();
            return optimizeStorageTableInfo.getColumnName(DbTableInfo.COLUMN_ID.SIZE);
        }

        @Override
        public String getNameColumnName() {
            DbTableInfo optimizeStorageTableInfo = OptimizeStorageDbTableInfo.getInstance();
            return optimizeStorageTableInfo.getColumnName(DbTableInfo.COLUMN_ID.NAME);
        }

        @Override
        public String getAllSizeSelection() {
            String sizeColumnName = getSizeColumnName();
            return sizeColumnName + " > 0) GROUP BY (" + sizeColumnName;
        }

        @Override
        public String getDuplicatedFileGroupInfoSelection(boolean isRefresh) {
            DbTableInfo tableInfo = OptimizeStorageDbTableInfo.getInstance();
            String sizeColumnName = getSizeColumnName();
            String subGroupIdColumnName = tableInfo.getColumnName(DbTableInfo.COLUMN_ID.SUB_GROUP_ID);
            return sizeColumnName + " >= " + DUPLICATED_FILE_MINIMUM_SIZE + ") GROUP BY " +
                    sizeColumnName + ", " + subGroupIdColumnName +
                    " HAVING (COUNT(*) > " + (isRefresh ? '1' : '0');
        }

        @Override
        public String getDuplicatedFileGroupInfoOrderBy() {
            DbTableInfo tableInfo = OptimizeStorageDbTableInfo.getInstance();
            String subGroupIdColumnName = tableInfo.getColumnName(DbTableInfo.COLUMN_ID.SUB_GROUP_ID);
            return getSizeColumnName() + " desc, " + subGroupIdColumnName + " asc";
        }
    }



    protected abstract static class CloudGatewayQueryInfo implements QueryInfo {

        protected Context mContext;


        public CloudGatewayQueryInfo(Context context) {
            mContext = context;
        }

        @Override
        public String getSizeColumnName() {
            DbTableInfo tableInfo = CloudDbTableInfo.getInstance();
            return tableInfo.getColumnName(DbTableInfo.COLUMN_ID.SIZE);
        }

        @Override
        public String getNameColumnName() {
            DbTableInfo tableInfo = CloudDbTableInfo.getInstance();
            return tableInfo.getColumnName(DbTableInfo.COLUMN_ID.NAME);
        }

        @Override
        public String getAllSizeSelection() {
            String sizeColumnName = getSizeColumnName();
            return sizeColumnName + " > 0 GROUP BY " + sizeColumnName;
        }

        @Override
        public String getDuplicatedFileGroupInfoSelection(boolean isRefresh) {
            String sizeColumnName = getSizeColumnName();
            return sizeColumnName + " >= " + DUPLICATED_FILE_MINIMUM_SIZE +
                    " GROUP BY " + sizeColumnName + " HAVING (COUNT(*) > " + (isRefresh ? '1' : '0') + ")";
        }

        @Override
        public String getDuplicatedFileGroupInfoOrderBy() {
            return getSizeColumnName() + " desc";
        }
    }

    protected static class GoogleDriveQueryInfo extends CloudGatewayQueryInfo {

        public GoogleDriveQueryInfo(Context context) {
            super(context);
        }

        @Override
        public StorageDbType getStorageDbType() {
            return StorageDbType.CLOUD_GOOGLE_DRIVE;
        }

        @Override
        public Uri getUri() {
            return CloudGatewayMediaStore.CloudFiles.getCloudFileUri(
                    CloudMgr.getInstance(mContext).getDeviceId(FileRecord.CloudType.GoogleDrive));
        }
    }

    protected static class SamsungDriveQueryInfo extends CloudGatewayQueryInfo {

        public SamsungDriveQueryInfo(Context context) {
            super(context);
        }

        @Override
        public StorageDbType getStorageDbType() {
            return StorageDbType.CLOUD_SAMSUNG_DRIVE;
        }

        @Override
        public Uri getUri() {
            return CloudGatewayMediaStore.CloudFiles.getCloudFileUri(
                    CloudMgr.getInstance(mContext).getDeviceId(FileRecord.CloudType.SamsungDrive));
        }

        @Override
        public String getAllSizeSelection() {
            String sizeColumnName = getSizeColumnName();
            return sizeColumnName + " > 0) GROUP BY (" + sizeColumnName;
        }

        @Override
        public String getDuplicatedFileGroupInfoSelection(boolean isRefresh) {
            String sizeColumnName = getSizeColumnName();
            return sizeColumnName + " >= " + DUPLICATED_FILE_MINIMUM_SIZE +
                    ") GROUP BY " + sizeColumnName + " HAVING (COUNT(*) > " + (isRefresh ? '1' : '0');
        }
    }





    public static class GroupInfo {
        public ArrayList<String> mGroupNames;
        public int[] mGroupChildCount;
    }

    public interface GroupInfoQuery {
        String getRangeQuery(Context context, String selection);
        GroupInfo getGroupInfo(Context context, Cursor c);
    }

    public static class LargeFilesGroupInfoQuery implements GroupInfoQuery {

        private static final int GROUP_COUNT = 50;
        private static final int STEP_SIZE = 100; // 100 MB

        private static long[] getLargeFilesSectionRange(Context context) {
            long[] ret = new long[GROUP_COUNT + 1];
            long largeFileSizeBytes = PreferenceUtils.getLargeFilesSize(context);
            int largeFileSize = (int) (largeFileSizeBytes / FileUtils.MEGA_BYTES);
            int firstStepSize = largeFileSize / STEP_SIZE;
            ret[ret.length - 1] = largeFileSizeBytes;
            for (int i = 1; i < ret.length; i++) {
                ret[ret.length - 1 - i] = (firstStepSize + i) * STEP_SIZE * FileUtils.MEGA_BYTES;
            }
            return ret;
        }

        /**
         * full query
         *
         * [Large]
         * select t.range as [group name], count(*) as [child count]
         *     from
         *         (select
         *             case
         *                 when size > 1073741824 then 'GROUP000'
         *                 when size between 104857600 and 1073741824 then 'GROUP001'
         *                 when size between 10485760 and 104857600 then 'GROUP002'
         *                 when size < 10485760 then 'GROUP003'
         *                 else 'GROUP999'
         *             end as range
         *         from optimize_storage where size is not null) t
         *     group by t.range order by t.range asc
         */
        @Override
        public String getRangeQuery(Context context, String selection) {
            DbTableInfo tableInfo = OptimizeStorageDbTableInfo.getInstance();
            StringBuilder query = new StringBuilder();
            query.append(" select case");
            final long[] ranges = getLargeFilesSectionRange(context);
            for (int i = 0; i < ranges.length; i++) {
                if (i == ranges.length - 1) {
                    if (i > 0) {
                        query.append(" when size between ").append(ranges[i]).append(" and ").append(ranges[i - 1]).append(" then ").append(String.format(Locale.getDefault(), "'%03d'", i));
                    }
                    query.append(" when size < ").append(ranges[i]).append(" then ").append(String.format(Locale.getDefault(), "'%03d'", i + 1));
                } else if (i == 0) {
                    query.append(" when size > ").append(ranges[i]).append(" then ").append(String.format(Locale.getDefault(), "'%03d'", i));
                } else {
                    query.append(" when size between ").append(ranges[i]).append(" and ").append(ranges[i - 1]).append(" then ").append(String.format(Locale.getDefault(), "'%03d'", i));
                }
            }
            query.append(" end as range");
            query.append(" from optimize_storage where ")
                    .append(selection).append(" and ")
                    .append(tableInfo.getColumnName(DbTableInfo.COLUMN_ID.SIZE)).append(" is not null");
            return query.toString();
        }

        @Override
        public GroupInfo getGroupInfo(Context context, Cursor c) {
            GroupInfo ret = null;
            if (c != null && c.moveToFirst()) {
                ret = new GroupInfo();
                ret.mGroupNames = new ArrayList<>();
                ret.mGroupChildCount = new int[c.getCount()];
                final long[] ranges = getLargeFilesSectionRange(context);
                int index = 0;
                do {
                    String strGroupIndex = c.getString(0);
                    int groupIndex = Integer.valueOf(strGroupIndex);
                    if (groupIndex < ranges.length) {
                        ret.mGroupNames.add(context.getString(R.string.over_ps,
                                Formatter.formatShortFileSize(context, ranges[groupIndex])));
                    } else {
                        ret.mGroupNames.add(context.getString(R.string.others));
                    }
                    ret.mGroupChildCount[index++] = c.getInt(1);
                } while (c.moveToNext());
            }
            return ret;
        }
    }

    public static class UnusedFilesGroupInfoQuery implements GroupInfoQuery {

        private static final int PERIOD = 12 * 5; // 5 years
        private static final long MONTH = 30 * 24 * 60 * 60;  // UNIT sec

        private static long[] getUnusedFilesSectionRange(Context context) {
            long[] ret = new long[PERIOD + 1];
            int unusedFilesPeriod = PreferenceUtils.getUnusedFilesPeriod(context);
            for (int i = 0; i < ret.length; i++) {
                ret[ret.length -1 - i] = (unusedFilesPeriod + i) * MONTH;
            }
            return ret;
        }

        private static String getUnusedPeriod(Context context) {
            DbTableInfo tableInfo = OptimizeStorageDbTableInfo.getInstance();
            StringBuilder builder = new StringBuilder("(");
            builder.append(System.currentTimeMillis() / 1000).append("-");
            String dateColumnName = tableInfo.getColumnName(DbTableInfo.COLUMN_ID.DATE);
            if (AppFeatures.isSupportDataAccessed(context)) {
                String dateAccessedColumnName = tableInfo.getColumnName(DbTableInfo.COLUMN_ID.DATE_ACCESSED);
                builder.append("(case when ").append(dateAccessedColumnName).append(" > ").append(dateColumnName)
                        .append(" then ").append(dateAccessedColumnName).append(" ELSE ").append(dateColumnName).append(" END)");
            } else {
                builder.append(dateColumnName);
            }
            builder.append(")");
            return builder.toString();
        }

        /**
         * full query
         *
         * select t.range as [group name], count(*) as [child count]
         *     from (
         *         select
         *             case
         *                 when
         *                     ([current time]-(case
         *                                     when date_accessed > date then date_accessed
         *                                     ELSE date
         *                                 END))
         *                     between [6 months] and [7 months] then "GROUP0"
         *
         *                 when
         *                     ([current time]-(case
         *                                     when date_accessed > date then date_accessed
         *                                     ELSE date
         *                                 END))
         *                     between [7 months] and [8 months] then "GROUP1"
         *
         *                 when
         *                     ([current time]-(case
         *                                     when date_accessed > date then date_accessed
         *                                     ELSE date
         *                                 END))
         *                     > [8 months] then "GROUP2"
         *             end as range
         *         from files) t
         *
         *     group by t.range order by t.range asc
         */
        @Override
        public String getRangeQuery(Context context, String selection) {
            StringBuilder query = new StringBuilder();
            String unusedPeriod = getUnusedPeriod(context);
            long[] sectionList = getUnusedFilesSectionRange(context);
            query.append(" select case");
            query.append(" when ").append(unusedPeriod).append(" > ").append(sectionList[0]).append(" then ").append("000");
            for (int i = 1; i < sectionList.length; i++) {
                query.append(" when ").append(unusedPeriod).append(" between ").append(sectionList[i]).append(" and ").append(sectionList[i - 1]).append(" then ").append(String.format(Locale.getDefault(), "'%03d'", i));
            }
            query.append(" end as range");
            query.append(" from optimize_storage where ")
                    .append(selection).append(" and ")
                    .append(unusedPeriod).append(" >= ").append(sectionList[sectionList.length - 1]);
            return query.toString();
        }

        @Override
        public GroupInfo getGroupInfo(Context context, Cursor c) {
            GroupInfo ret = null;
            if (c != null && c.moveToFirst()) {
                ret = new GroupInfo();
                ret.mGroupNames = new ArrayList<>();
                ret.mGroupChildCount = new int[c.getCount()];
                long[] sectionList = getUnusedFilesSectionRange(context);
                int index = 0;
                do {
                    String strGroupIndex = c.getString(0);
                    int groupIndex = Integer.valueOf(strGroupIndex);
                    if (groupIndex < sectionList.length) {
                        int months = (int) (sectionList[groupIndex] / MONTH);
                        int years = months / 12;
                        boolean displayMonth = (months % 12 != 0);
                        String name;
                        if (displayMonth) {
                            name = context.getResources().getQuantityString(R.plurals.n_months_since_last_used, months, months);
                        } else {
                            name = context.getResources().getQuantityString(R.plurals.n_years_since_last_used, years, years);
                        }
                        ret.mGroupNames.add(name);
                    }
                    ret.mGroupChildCount[index++] = c.getInt(1);
                } while (c.moveToNext());
            }
            return ret;
        }
    }

    public static class UnnecessaryFilesGroupInfoQuery implements GroupInfoQuery {

        private static final String TAG_LOG_FILES = "GROUP000";
        private static final String TAG_TEMP_FILES = "GROUP001";
        private static final String TAG_EMPTY_FILES = "GROUP002";
        private static final String TAG_ETC = "GROUP999";

        @Override
        public String getRangeQuery(Context context, String selection) {
            StringBuilder query = new StringBuilder();
            query.append(" select case");
            query.append(" when ext='log' then '" + TAG_LOG_FILES + "'");
            query.append(" when ext='tmp' then '" + TAG_TEMP_FILES + "'");
            query.append(" when ext!='log' and ext!='tmp' and name!='.nomedia' and size=0 then '" + TAG_EMPTY_FILES + "'");
            query.append(" else '" + TAG_ETC + "'");
            query.append(" end as range");
            query.append(" from optimize_storage where ").append(selection);
            return query.toString();
        }

        @Override
        public GroupInfo getGroupInfo(Context context, Cursor c) {
            GroupInfo ret = null;
            if (c != null && c.moveToFirst()) {
                ret = new GroupInfo();
                ret.mGroupNames = new ArrayList<>();
                ArrayList<Integer> groupChildCount = new ArrayList<>();
                do {
                    String tag = c.getString(0);
                    int count = c.getInt(1);
                    if (count > 0) {
                        if (TAG_LOG_FILES.equals(tag)) {
                            ret.mGroupNames.add(context.getString(R.string.log_files));
                        } else if (TAG_TEMP_FILES.equals(tag)) {
                            ret.mGroupNames.add(context.getString(R.string.temporary_files));
                        } else if (TAG_EMPTY_FILES.equals(tag)) {
                            ret.mGroupNames.add(context.getString(R.string.empty_files));
                        } else {
                            ret.mGroupNames.add(context.getString(R.string.unknown));
                        }
                        groupChildCount.add(count);
                    }
                } while (c.moveToNext());

                if (!groupChildCount.isEmpty()) {
                    int childCountLength = groupChildCount.size();
                    ret.mGroupChildCount = new int[childCountLength];
                    for (int i = 0; i < childCountLength; i++) {
                        ret.mGroupChildCount[i] = groupChildCount.get(i);
                    }
                }
            }
            return ret;
        }
    }
}
